#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <limits.h>

#include <tcl.h>
#include <tk.h>

#include "asmCalc.h"
#include "gui.h"

/*******************************
*          UPDATE GUI          *
*******************************/

static void updateGuiFlags_x86( struct instance* this, Tcl_Interp* tcl ){
	
	const char* CF;//0  CF Carry Flag
	const char* PF;//2  PF Parity Flag
	const char* AF;//4  AF Auxiliary Carry Flag
	const char* ZF;//6  ZF Zero Flag
	const char* SF;//7  SF Sign Flag
	const char* OF;//11 OF Overflow Flag
	
	CF = this->cpuFlags &   1         ? "1" : "0";
	PF = this->cpuFlags & ( 1 << 2 )  ? "1" : "0";
	AF = this->cpuFlags & ( 1 << 4 )  ? "1" : "0";
	ZF = this->cpuFlags & ( 1 << 6 )  ? "1" : "0";
	SF = this->cpuFlags & ( 1 << 7 )  ? "1" : "0";
	OF = this->cpuFlags & ( 1 << 11 ) ? "1" : "0";
	
	Tcl_SetVar( tcl, "cpuFlagCf", CF, 0 );
	Tcl_SetVar( tcl, "cpuFlagPf", PF, 0 );
	Tcl_SetVar( tcl, "cpuFlagAf", AF, 0 );
	Tcl_SetVar( tcl, "cpuFlagZf", ZF, 0 );
	Tcl_SetVar( tcl, "cpuFlagSf", SF, 0 );
	Tcl_SetVar( tcl, "cpuFlagOf", OF, 0 );
	
}

static void updateGuiReg( struct instance* this, Tcl_Interp* tcl, int regno, char ignore ){
	
	int i;
	int bitno;
	int byteno;
	char buf[1024];
	int bufOffset;
	const char* bitval;
	int bytes;
	
	bytes = this->ops->regSize / 8;
	
	//signed
	if( ignore != 's' ){
		if(      bytes == 1 ) sprintf( buf, "%hhi", this->regs[regno].s8  );
		else if( bytes == 2 ) sprintf( buf, "%hi",  this->regs[regno].s16 );
		else if( bytes == 4 ) sprintf( buf, "%i",   this->regs[regno].s32 );
		else                  sprintf( buf, "%lli", this->regs[regno].s64 );
		Tcl_SetVar( tcl, tclRegVars[regno].sig, buf, 0 );
	}
	
	//unsigned
	if( ignore != 'u' ){
		if(      bytes == 1 ) sprintf( buf, "%hhu", this->regs[regno].u8  );
		else if( bytes == 2 ) sprintf( buf, "%hu",  this->regs[regno].u16 );
		else if( bytes == 4 ) sprintf( buf, "%u",   this->regs[regno].u32 );
		else                  sprintf( buf, "%llu", this->regs[regno].u64 );
		Tcl_SetVar( tcl, tclRegVars[regno].unsig, buf, 0 );
	}
	
	//hex
	if( ignore != 'h' ){
		
		bufOffset = 0;
		for( i = 0; i < bytes; i++ ){
			bufOffset += sprintf( buf + bufOffset, "0x%02X ", this->regs[regno].bytes[i] );
		}
		Tcl_SetVar( tcl, tclRegVars[regno].hex, buf, 0 );
	}
	
	//bytes and bits
	if( ignore != 'b' ){
		for( byteno = 0; byteno < bytes; byteno++ ){
			for( bitno = 0; bitno < 8; bitno++ ){
				if( this->regs[regno].bytes[byteno] & (1 << bitno) ) bitval = "1";
				else bitval = "0";
				Tcl_SetVar( tcl, tclRegVars[regno].bytes[byteno][bitno], bitval, 0 );
			}
		}
	}
}

/*************************
*          EDIT          *
*************************/

static int regRstCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	const char* _regno;
	unsigned int regno;
	struct instance* this;
	
	this = clientData;
	
	//check arg count
	if( objc != 2 ){
		Tcl_SetResult( tcl, "regRstCore: unexpected num of args.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//get args from tcl
	_regno  = Tcl_GetString( objv[1] );
	
	//check they not null
	if( !_regno ){
		Tcl_SetResult( tcl, "editRegByteCore: regno is NULL.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//chek they valid
	if( sscanf( _regno,  "%d", &regno  ) != 1 ) {
		Tcl_SetResult( tcl, "editRegByteCore: regno is invalid.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//check they in acceptable limits
	if( regno > 2 ) {
		Tcl_SetResult( tcl, "editRegByteCore: regno is out of range.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//set reg val to 0
	this->regs[regno].u64 = 0;
	
	//update gui
	updateGuiReg( this, tcl, regno, 0 );
	
	return TCL_OK;
}

static int editRegCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	int res;
	int bytes;
	const char* _regno;
	const char* field;
	const char* newVal;
	unsigned int regno;
	
	struct instance* this;
	
	union {
		unsigned char b[8];
		signed   long long int s64;
		unsigned long long int u64;
		signed   int s32;
		unsigned int u32;
		signed short   int s16;
		unsigned short int u16;
		signed   char s8;
		unsigned char u8;
	} tmpreg;
	
	memset( &tmpreg, 0, sizeof(tmpreg) );
	
	//init some vars
	res = 0;
	this = clientData;
	bytes = this->ops->regSize / 8;
	
	//check args count
	if( objc != 4 ){
		Tcl_SetResult( tcl, "editRegCore: unexpected num of args.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//get args from tcl
	_regno = Tcl_GetString( objv[1] );
	field  = Tcl_GetString( objv[2] );
	newVal = Tcl_GetString( objv[3] );
	
	//check they not null
	if( !_regno || !field || !newVal ){
		Tcl_SetResult( tcl, "editRegCore: some args is NULL.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//check regno is valid
	if( sscanf(_regno, "%d", &regno) != 1 ){
		Tcl_SetResult( tcl, "editRegCore: regno is out of range.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//check regno in acceptable limits
	if( regno > 2 ) {
		Tcl_SetResult( tcl, "editRegByteCore: regno is out of range.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	if( field[0] == 's' ){//signed input
		if(      bytes == 1 ) res = sscanf( newVal, "%hhd",&tmpreg.s8  );
		else if( bytes == 2 ) res = sscanf( newVal, "%hd", &tmpreg.s16 );
		else if( bytes == 4 ) res = sscanf( newVal, "%d",  &tmpreg.s32 );
		else                  res = sscanf( newVal, "%lld",&tmpreg.s64 );
		if( res == 1 ){
			this->regs[regno].s64 = tmpreg.s64;
			updateGuiReg(this, tcl, regno, 's' );
		}
	}else if( field[0] == 'u' ){//unsigned input
		if(      bytes == 1 ) res = sscanf( newVal, "%hhu", &tmpreg.u8  );
		else if( bytes == 2 ) res = sscanf( newVal, "%hu",  &tmpreg.u16 );
		else if( bytes == 4 ) res = sscanf( newVal, "%u",   &tmpreg.u32 );
		else                  res = sscanf( newVal, "%llu", &tmpreg.u64 );
		if( res == 1 ){
			this->regs[regno].u64 = tmpreg.u64;
			updateGuiReg(this, tcl, regno, 'u' );
		}
	}else if( field[0] == 'h' ){//hex input
		if(      bytes == 1 ) res = sscanf( newVal, "0x%02hhX", &tmpreg.b[0] );
		else if( bytes == 2 ) res = sscanf( newVal, "0x%02hhX 0x%02hhX", &tmpreg.b[0], &tmpreg.b[1] );
		else if( bytes == 4 ) res = sscanf( newVal, "0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX", &tmpreg.b[0], &tmpreg.b[1], &tmpreg.b[2], &tmpreg.b[3] );
		else res = sscanf( newVal, "0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX 0x%02hhX",
		&tmpreg.b[0], &tmpreg.b[1], &tmpreg.b[2], &tmpreg.b[3], &tmpreg.b[4], &tmpreg.b[5], &tmpreg.b[6], &tmpreg.b[7] );
		if( res > 0 ){
			this->regs[regno].u64 = tmpreg.u64;
			updateGuiReg( this, tcl, regno, 'h' );
		}
	} else {
		Tcl_SetResult( tcl, "editRegCore: unexpected field id.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	Tcl_SetResult( tcl, "1", TCL_STATIC );
	
	return TCL_OK;
}

static int editRegByteCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	const char* _regno;
	const char* _byteno;
	const char* _bitno;
	unsigned int regno;
	unsigned int byteno;
	unsigned int bitno;
	const char* bitval;
	struct instance* this;
	
	this = clientData;
	
	//"editRegByte $regno $byteno $bitno"
	
	//check arg count
	if( objc != 4 ){
		Tcl_SetResult( tcl, "editRegByteCore: unexpected num of args.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//get args from tcl
	_regno  = Tcl_GetString( objv[1] );
	_byteno = Tcl_GetString( objv[2] );
	_bitno  = Tcl_GetString( objv[3] );
	
	//check they not null
	if( !_regno || !_byteno || !_bitno ){
		Tcl_SetResult( tcl, "editRegByteCore: some vars is NULL.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//chek they valid
	if( 
		sscanf( _regno,  "%d", &regno  ) != 1 || 
		sscanf( _byteno, "%d", &byteno ) != 1 || 
		sscanf( _bitno,  "%d", &bitno  ) != 1 ) {
		Tcl_SetResult( tcl, "editRegByteCore: some vars is invalid.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//check they in acceptable limits
	if( regno > 2 || byteno > 7 || bitno > 7 ) {
		Tcl_SetResult( tcl, "editRegByteCore: some vars is out of range.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//get new value of bit
	bitval = Tcl_GetVar( tcl, tclRegVars[regno].bytes[byteno][bitno], 0 );
	
	//change bit value in register
	if( bitval[0] == '0' ) this->regs[regno].bytes[byteno] &= ~(1 << bitno);
	else this->regs[regno].bytes[byteno] |= 1 << bitno;
	
	//update gui
	updateGuiReg( this, tcl, regno, 'b' );
	
	return TCL_OK;
}

static int setOutputRegCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	const char* _regno;
	unsigned int regno;
	struct instance* this;
	
	this = clientData;
	
	//get regno from tcl
	_regno = Tcl_GetVar( tcl, "outputReg", 0 );
	
	//check regno not null
	if( !_regno  ){
		Tcl_SetResult( tcl, "editRegByteCore: regno is NULL.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//chek regno valid
	if( sscanf( _regno,  "%d", &regno  ) != 1 ) {
		Tcl_SetResult( tcl, "editRegByteCore: regno is invalid.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//check regno in acceptable limits
	if( regno > 2  ) {
		Tcl_SetResult( tcl, "editRegByteCore: regno is out of range.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	//set output reg
	this->outReg   = &this->regs[regno];
	this->outRegNo = regno;
	
	return TCL_OK;
}

static int setRegSizeCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	int i;
	int res;
	struct instance* this;
	const char* _regSize;
	unsigned int regSize;
	
	this = clientData;
	
	_regSize = Tcl_GetVar( tcl, "regSize", 0 );
	if( !_regSize ){
		Tcl_SetResult( tcl, "editRegByteCore: regSize is NULL.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	res = sscanf( _regSize, "%ubit", &regSize );
	if( res != 1 ){
		Tcl_SetResult( tcl, "editRegByteCore: regSize is invalid.", TCL_STATIC );
		return TCL_ERROR;
	} 
	
	res = setHostAsmOps( this, regSize );
	if( res < 0 ){
		Tcl_SetResult( tcl, "editRegByteCore: this regSize is not available on this system.", TCL_STATIC );
		return TCL_ERROR;
	}
	
	for( i = 0; i < 3; i++ ) updateGuiReg( this, tcl, i, 0 );
	
	return TCL_OK;
}

/******************************
*          OPERAIONS          *
******************************/

//math
static int opAddCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->add( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opSubCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->sub( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opMulCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->mul( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opImulCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->imul( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opDivCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	if( !this->regs[1].u64 ){
		Tcl_Eval( tcl, "tk_messageBox -icon warning -title Error -message { Illegal instruction } -detail { Division by zero }" );
		return TCL_OK;
	}
	
	this->ops->div( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opIdivCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	if( !this->regs[1].u64 ){
		Tcl_Eval( tcl, "tk_messageBox -icon warning -title Error -message { Illegal instruction } -detail { Division by zero }" );
		return TCL_OK;
	}
	
	this->ops->idiv( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opNegCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->neg( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

//logic
static int opAndCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->and( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opNotCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->not( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opOrCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->or( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opXorCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->xor( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

//shift
static int opShlCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->shl( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opShrCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->shr( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opSalCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->sal( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opSarCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->sar( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opRolCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->rol( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opRorCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->ror( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

//other
static int opBswapCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->bswap( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

static int opMovsxCore( ClientData clientData, Tcl_Interp* tcl, int objc, Tcl_Obj* const objv[] ){
	
	struct instance* this = clientData;
	
	this->ops->movsx( this );
	updateGuiReg( this, tcl, this->outRegNo, 0 );
	updateGuiFlags_x86( this, tcl );
	
	return TCL_OK;
}

/*************************
*          MAIN          *
*************************/

static int getTclFile( char* argv0, char* buf ){
	
	char* cres;
	char tmp1[PATH_MAX];
	char* dir;
	
	//get full path to asmCalc
	cres = realpath( argv0, tmp1 );
	if( !cres ){
		perror( "realpath" );
		return -2;
	}
	
	//get working dir
	dir = dirname( tmp1 );
	
	sprintf( buf, "%s/asmCalc.tcl", dir );
	
	return 0;
}

int main ( int argc, char* argv[] ){
	
	int i;
	int res;
	Tcl_Interp* tcl;
	struct instance this;
	char tmpbuf[32];
	char tclFile[PATH_MAX + 32];
	
	union {
		short s;
		char  c[2];
	} hec;
	
	//init some vars
	memset( &this, 0, sizeof(this) );
	this.outReg = &this.regs[0];
	
	res = getTclFile( argv[0], tclFile );
	if( res ) return 1;
	
	//init tcl/tk
	Tcl_FindExecutable( argv[0] );
	tcl = Tcl_CreateInterp();
	Tcl_Init( tcl );
	Tk_Init(  tcl );
	
	//init register size
	res = setHostAsmOps( &this, -1 );
	sprintf( tmpbuf, "%ibit", res );
	Tcl_SetVar( tcl, "regSize", tmpbuf, 0 );
	
	//load main window layout
	res = Tcl_EvalFile( tcl, tclFile );
	if( res ){
		printf( "failed to eval asmCalc.tcl\n" );
		goto l_cleanup;
	}
	
	//setup callbacks for functions in tcl
	for( i = 0; tclCallbacks[i].tclFunc; i++ ){
		Tcl_CreateObjCommand( tcl, tclCallbacks[i].tclFunc, tclCallbacks[i].cFunc, &this, NULL );
	}
	
	//update gui to init values
	for( i = 0; i < 3; i++ ) updateGuiReg( &this, tcl, i, 0 );
	
	//print host endian
	hec.s = 1;
	Tcl_SetVar( tcl, "hostEndianText", hec.c[0] ? "LE" : "BE", 0 );
	
	//mait gui loop
	Tk_MainLoop();
	
l_cleanup:
	
	Tcl_DeleteInterp( tcl );
	
	//printf( "Exit.\n" );
	return 0;
}
